在前幾天的學習中,我們已掌握了 Locust 的基本用法,並學會管理 Cookie 和 Session。今天,將深入探討一個在實際測試中至關重要的主題:參數化測試。透過參數化,可以讓測試腳本變得更加真實、多樣化,進而模擬不同使用者的行為模式,建立更動態且靈活的壓力測試。
在真實的應用場景中,使用者的行為總是充滿多樣性。如果測試總是使用相同的資料或請求路徑,測試結果可能無法真實反映系統在實際使用中的表現。參數化測試能有效解決以下問題:
真實使用者會使用不同的帳號、查詢不同的資料、輸入各種參數。固定的測試資料無法模擬這種多樣性。
# ❌ 不真實的測試 - 所有使用者都查詢相同資料
@task
def search_products(self):
response = self.client.get("/search?q=iPhone") # 總是搜尋 iPhone
# ✅ 真實的測試 - 使用者搜尋不同商品
@task
def search_products(self):
search_terms = ["iPhone", "Samsung", "iPad", "MacBook", "AirPods"]
query = random.choice(search_terms)
response = self.client.get(f"/search?q={query}")
不同的資料可能會觸發系統不同的處理邏輯。使用多樣化的測試資料能夠更全面地測試系統,發現潛在的效能瓶頸或邏輯錯誤。
如果所有請求都使用相同的參數,系統的快取機制可能會產生誤導性的測試結果,導致測試不準確。
# ❌ 可能被快取影響
@task
def get_user_profile(self):
response = self.client.get("/users/123") # 總是查詢相同使用者
# ✅ 避免快取影響
@task
def get_user_profile(self):
user_id = random.randint(1, 10000)
response = self.client.get(f"/users/{user_id}")
參數化測試可以系統性地涵蓋各種邊界條件和異常情境,確保系統的魯棒性。
Locust 提供了多種參數化方式,讓你可以靈活地為測試提供不同的資料。以下是一些常見方法:
最簡單的參數化方式是使用列表、字典等資料結構,並結合 random
模組進行隨機選擇。
import random
from locust import HttpUser, task, between
class BasicParameterizedUser(HttpUser):
wait_time = between(1, 3)
host = "http://localhost:8080"
# 定義測試資料
search_keywords = ["Python", "JavaScript", "Java", "Go", "Rust"]
product_categories = ["electronics", "books", "clothing", "home", "sports"]
@task(3)
def search_with_random_keyword(self):
"""使用隨機關鍵字搜尋"""
keyword = random.choice(self.search_keywords)
self.client.get(f"/search?q={keyword}")
@task(2)
def browse_category(self):
"""瀏覽隨機分類"""
category = random.choice(self.product_categories)
page = random.randint(1, 10)
self.client.get(f"/category/{category}?page={page}")
在某些情況下,我們希望某些參數出現的頻率更高,以模擬真實世界中的使用者行為模式。
import random
from locust import HttpUser, task, between
class WeightedParameterizedUser(HttpUser):
wait_time = between(1, 2)
host = "http://localhost:8080"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 使用加權列表 - 模擬真實使用頻率
self.search_terms = [
("iPhone", 30), # 高頻搜尋
("Samsung", 25),
("iPad", 20),
("MacBook", 15),
("AirPods", 10) # 低頻搜尋
]
# 建立加權選擇列表
self.weighted_terms = []
for term, weight in self.search_terms:
self.weighted_terms.extend([term] * weight)
@task
def weighted_search(self):
"""根據權重進行搜尋"""
search_term = random.choice(self.weighted_terms)
self.client.get(f"/search?q={search_term}")
itertools
進行循環參數化當我們希望按順序使用參數時,可以使用 itertools.cycle
建立一個無限循環的迭代器。
import itertools
from locust import HttpUser, task, between
class CyclicParameterizedUser(HttpUser):
wait_time = between(1, 2)
host = "http://localhost:8080"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 建立循環迭代器
self.user_ids = itertools.cycle(range(1, 101)) # 循環使用 1-100
self.api_versions = itertools.cycle(["v1", "v2", "v3"])
@task
def cyclic_user_request(self):
"""循環使用使用者 ID"""
user_id = next(self.user_ids)
api_version = next(self.api_versions)
headers = {"API-Version": api_version}
self.client.get(f"/users/{user_id}", headers=headers)
對於需要大量且結構化的測試資料,CSV 檔案 是最實用的方法。它易於管理,並能容納龐大的資料集。
首先,建立一個名為 test_users.csv
的檔案,內容如下:
user_id,username,email,user_type,region,preferred_language
1,john_doe,john@example.com,premium,US,en
2,alice_wang,alice@example.com,basic,TW,zh-TW
...
接著,在 Locust 腳本中加入讀取 CSV 檔案的邏輯:
import csv
import random
import itertools
from locust import HttpUser, task, between
class CSVParameterizedUser(HttpUser):
wait_time = between(1, 3)
host = "http://localhost:8080"
# 類別變數,所有使用者實例共享
test_users = []
@classmethod
def load_test_data(cls):
"""載入 CSV 測試資料"""
try:
with open('test_users.csv', 'r', encoding='utf-8') as file:
reader = csv.DictReader(file)
cls.test_users = list(reader)
print(f"成功載入 {len(cls.test_users)} 筆測試資料")
except FileNotFoundError:
print("找不到 test_users.csv 檔案,使用預設資料")
cls.test_users = [{"user_id": "1", "username": "test_user"}]
def on_start(self):
"""使用者開始時載入資料"""
# 在第一個使用者實例化時載入資料
if not self.__class__.test_users:
self.__class__.load_test_data()
# 建立使用者特定的資料迭代器
self.user_data = next(itertools.cycle(self.__class__.test_users))
@task(3)
def get_user_profile(self):
"""使用 CSV 資料獲取使用者個人資料"""
self.client.get(f"/users/{self.user_data['user_id']}")
@task(2)
def update_preferences(self):
"""根據 CSV 資料更新使用者偏好設定"""
user_data = random.choice(self.__class__.test_users)
update_data = {
"language": user_data['preferred_language'],
"region": user_data['region'],
}
self.client.put(
f"/users/{user_data['user_id']}/preferences",
json=update_data
)
提示: 在上述範例中,
test_users
被設為類別變數,並在on_start
或__init__
中以類別方法的方式載入資料,這能有效避免在每次創建使用者時都重複讀取檔案,大幅提升效能。
今天我們深入學習了 Locust 中的參數化測試技術:
透過掌握這些參數化技術,你現在能夠建立更真實的測試場景、管理大量的測試資料、並提升測試的準確性。參數化是進階壓力測試的核心技能,正確使用這些技術將能讓你的測試更貼近真實世界的使用情況,從而發現更多潛在問題並提供更可靠的測試結果。